package org.snp

import com.sun.pdfview.PagePanel

import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import org.slf4j.LoggerFactory
import java.awt.{Graphics, AlphaComposite, Color, Rectangle, Graphics2D, RenderingHints, Font, BasicStroke, Point}

/**
 * We add the ability to select areas of a PDF - for the purpose of printing addresses into
 */
class BoxSelectPanel extends PagePanel with MouseListener with MouseMotionListener {
  // Are we drawing a boundary box? For selecting a region of the PDF.
  //created rectangles - all specified in page space
  private var rectangles: List[Rectangle] = Nil
  private var allFields: List[Option[Fields]] = Nil

  //Rectangle being created right now
  private var selectedBox: Rectangle = null

  //Rectangle being hoverred over by mouse pointer, if any
  private var hoverBox: Rectangle = null
  //cornflower blue
  private var boxColour: Color = new Color(6591981)
  private val fontColour: Color = new Color(0)

  //for drawing translucently
  private val transparentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4F);
  private val solidComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1F);

  private val log = LoggerFactory.getLogger(getClass);

  private var offsetx: Int = 0
  private var offsety: Int = 0

  //just add drawing of translucent selectedBoxs
  override def paint(g: Graphics) {
    super.paint(g)
    val g2d: Graphics2D = g.asInstanceOf[Graphics2D]

    val allRects = if (selectedBox != null) {
      selectedBox :: rectangles
    } else rectangles

    val fieldRects = allRects.sort(_.y > _.y).zip(allFields)

    fieldRects.foreach(f => adjustSize(g2d, f._1, f._2))

    if (selectedBox != null) {
      paintBox(g2d, selectedBox, true)
    }

    if (currentXform != null && rectangles.size > 0) {
      rectangles.foreach(r => paintBox(g2d, r, false))
    }

    fieldRects.foreach {
      fieldRect =>
        drawAddress(g2d, fieldRect._1, fieldRect._2)
    }
  }

  //TODO all these methods are on boxes, should probably make a box class and bung them in there

  //This paints filled rectangles specified in display space
  private def paintBox(g2d: Graphics2D, box: Rectangle, selected: Boolean) {
    val boxScrn = toScreenSpace(box)
    g2d.setColor(boxColour)
    g2d.setComposite(transparentComposite)
    g2d.fillRoundRect(boxScrn.x, boxScrn.y, boxScrn.width, boxScrn.height, fontHeight * 2, fontHeight * 2)
    g2d.setColor(fontColour)
    //draw a thick border
    if (selected || (hoverBox != null && hoverBox == box))
      g2d.setComposite(solidComposite)
    g2d.setStroke(new BasicStroke((fontHeight * 2) / 5))
    g2d.drawRoundRect(boxScrn.x, boxScrn.y, boxScrn.width, boxScrn.height, fontHeight * 2, fontHeight * 2)
  }

  private def drawAddress(g2d: Graphics2D, box: Rectangle, maybeFields: Option[Fields]) {
    val boxScrn = toScreenSpace(box)
    if (maybeFields.isEmpty)
      return
    val fields = maybeFields.get.fields
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    val font = new Font("Helvetica", Font.PLAIN, fontHeight.intValue);
    g2d.setFont(font);
    g2d.setComposite(solidComposite)
    g2d.setColor(fontColour)
    log.debug("box.height:" + boxScrn.height)
    log.debug("x " + boxScrn.x + (boxScrn.width) / 2.0 + " :Y:" + boxScrn.y + (boxScrn.height) / 2.0)
    val lineHeight = g2d.getFontMetrics(font).getHeight
    val startx = boxScrn.x + fontHeight / 2
    val starty = boxScrn.y + lineHeight + fontHeight / 2
    var i: Int = 0
    fields.foreach {
      field =>
        g2d.drawString(field, startx, starty + lineHeight * i)
        i += 1
    }
  }

  //adjust size of all the boxes so that they snugly fit the text + a border equal to half the font height all round
  private def adjustSize(g2d: Graphics2D, box: Rectangle, maybeFields: Option[Fields]) {
    if (maybeFields.isEmpty)
      return
    val fields = maybeFields.get.fields
    val font = new Font("Helvetica", Font.PLAIN, fontHeight.intValue);
    g2d.setFont(font);

    //max width of any of the fields, calculated via font metrics
    val maxWidth = fields.map(g2d.getFontMetrics(font).stringWidth(_)).reduceLeft((a, b) => if (a > b) a else b)
    val lineHeight = g2d.getFontMetrics(font).getHeight
    val totalHeight = lineHeight * fields.size + g2d.getFontMetrics(font).getDescent
    val boxScrn = toScreenSpace(box)
    boxScrn.width = maxWidth.intValue + fontHeight.intValue
    boxScrn.height = totalHeight + fontHeight.intValue
    box.setBounds(toPageSpace(boxScrn))
  }

  def toPageSpace(p: Point): Point = {
    currentXform.transform(p, p).asInstanceOf[Point]
  }

  def toScreenSpace(p: Point): Point = {
    currentXform.inverseTransform(p, p).asInstanceOf[Point]
  }

  //transformShape seems to screw with the size of the rectangle, so just doing these calculations myself
  def toScreenSpace(r: Rectangle): Rectangle = {
    var p1 = toScreenSpace(new Point(r.x, r.y))
    var p2 = toScreenSpace(new Point(r.x + r.width, r.y + r.height))
    new Rectangle(p1.x, p1.y, Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y))
  }

  def toPageSpace(r: Rectangle): Rectangle = {
    var p1 = toPageSpace(new Point(r.x, r.y))
    var p2 = toPageSpace(new Point(r.x + r.width, r.y + r.height))
    new Rectangle(p1.x, p1.y, Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y))
  }

  //For arguments (in page space) returns boxes which overlap that point
  def getBoxes(p: Point): List[Rectangle] = {
    rectangles.filter(box => {
      box.x <= p.x && (box.x + box.width) >= p.x && box.y >= p.y && (box.y - box.height) <= p.y
    })
  }

  //this is much the same as in superclass except we don't constrain to aspect ratio
  override def mouseDragged(evt: MouseEvent) {
    if (useZoom)
      super.mouseDragged(evt)
    else if (selectedBox != null) {
      //move selectedBox to pointer location
      val point = new Point(evt.getX - offsetx, evt.getY - offsety)
      selectedBox.setLocation(toPageSpace(point))
      //repaint all address boxes + union of moved box - might as well just blanket repaint whole screen
      repaint()
    }
  }

  //when pointer is hovering over a box, mark it selected as the hover box so we can paint it as such later
  override def mouseMoved(evt: MouseEvent) {
    val point = new Point(evt.getX, evt.getY)
    val boxes = getBoxes(toPageSpace(point))
    if (boxes != Nil) {
      if (hoverBox != boxes.head) {
        hoverBox = boxes.head
        //bring hoverbox to the front of the list so it stays selected till be roll off
        rectangles = hoverBox :: rectangles.filter(_ != hoverBox)
        repaint()
      }
    } else {
      if (hoverBox != null)
        repaint()
      hoverBox = null
    }
  }

  override def mousePressed(evt: MouseEvent) {
    val point = new Point(evt.getX, evt.getY)
    val boxes = getBoxes(toPageSpace(point))
    log.info("Overlapping boxes:" + boxes.size)
    if (boxes.size == 0)
      return

    rectangles = rectangles.filter(r => r != boxes.head)
    selectedBox = boxes.head
    val boxScrn = toScreenSpace(selectedBox)
    offsetx = evt.getX - boxScrn.x
    offsety = evt.getY - boxScrn.y
  }

  //add a new box, taking into account tiling behaviour for a nice UI
  def addBox(pos: Point, allFields: List[Option[Fields]]) {
    this.allFields = allFields
    //see if a box exists at the location we want to paint to! and tile if it does
    var boxOrigin = toPageSpace(new Point(pos.x + fontHeight * 5, pos.y + fontHeight * 5))

    while (rectangles.exists(r => r.x == boxOrigin.x && r.y == boxOrigin.y)) {
      val boxOriginScrn = toScreenSpace(boxOrigin)
      boxOriginScrn.x += fontHeight * 5
      boxOriginScrn.y += fontHeight * 5
      boxOrigin = toPageSpace(boxOriginScrn)
    }
    val box = new Rectangle(boxOrigin.x, boxOrigin.y, 150, 75)
    rectangles ::= box
    repaint()
  }

  override def mouseReleased(evt: MouseEvent) {
    if (useZoom)
      super.mouseReleased(evt)
    else if (selectedBox != null) {
      rectangles ::= selectedBox
      selectedBox = null
    }
  }

  private def fontHeight: Int = {
    (11 / currentPage.getHeight * currentImage.getHeight(this)).intValue
  }

  def points: List[Point] = {
    rectangles.map {
      r =>
        val rScrn = toScreenSpace(r)
        val pScrn = new Point(rScrn.x + fontHeight, rScrn.y + fontHeight)
        toPageSpace(pScrn)
    }
  }
}
